perm filename TCPIP.MAC[IP,SYS] blob sn#680221 filedate 1982-10-14 generic text, type T, neo UTF8
;CWL:<403-TCP>TCPIP.MAC.40303  6-May-82 15:55:31, Edit by CLYNN
; Work on windowing; newer round-trip-time algorithm
; Fix bad port numbers in SNDRST
;<403-TCP>TCPIP.MAC.40302 9-Mar-82 20:28:46, Edit by CLYNN
; Updated for TCP release 3
;[BBNF]<401-TCP>TCPIP.MAC.153, 21-Jul-81 11:37:00, Ed: CLYNN
; Fix: "No such TCB" check in INPRO4, TCP data test in PRCSY3,
; RX queue check at REMSE7, RSTADR, Treat RST to persistent
; open (w/timeout) as error at PRCRS1
;<MNET-TCP>TCPIP.MAC.2,  4-Apr-81 17:11:08, Edit by TAPPAN
; merge with multinet stuff

	SEARCH	INPAR,TCPPAR,PROLOG
	TTITLE	TCPIP
	SUBTTL	TCP Input Processor, Willaim W. Plummer, 27Jan77
	SWAPCD

COMMENT	!		Executed in Internet (JOB0) context
	The INPUTPROCESSOR is called to process the queue
	of packets just input from the network.  Each packet is checked
	for proper format, protocol version, checksum, etc and if
	all is OK, the correct TCB is looked up.  IP may respond
	with an RST packet if the TCB is not there, it may ACK
	the packet if it is a duplicate, or it may process some
	things from the packet and queue it for the REASSEMBLER.
	In particular, IP processes the data acknowledged by the
	packet and sets the new window for PZ.

* INPROC ...  3 ...... Scan queue.  Check packet.  Lookup TCB.
  INPUT ....  6 ...... 2nd phase.  Called with valid TCB, PKT

  CHKSEQ ...  8 ...... Determine if packet should be processes
  CKSYNS ...  9 ...... Check sequence when synchronized
  CKACKS ... 10 ...... Check sequence when getting synchonized

  PRCPKT ... 11 ...... Process the packet
  REMCHK ... 15 ...... See if packet can be removed from RA queue
  PRCRST ... 16 ...... Process RST packet
  PRCWND ... 17 ...... Process Window information
* PRCACK ... 19 ...... Process ACK
  PRCURG ... 23 ...... Process URG
  PRCSYN ... 24 ...... Process SYN

  SNDRST ... 25 ...... Send and RST packet

* REMSEQ ... 27 ...... Delete packets from queue between two limits
* ABTCON ... 30 ...... Abort a connection
  ABTCO1 ... 32 ...... Scan TCP buffer done queue for ABTCON
  SYNAGN ... 33 ...... Put connection back in syncable state
  RSTADR ... 35 ...... Restore wild foreign adr fields after false start
  IPINI  ... 36 ...... Initialize Input Processor Process block
	!
; Process the input packet queue.
; Packets have been queue by IMPDV interrupt level.

;	CALL INPROC
;Ret+1:	always.

INPROC::STACKL <<ARGBLK,CHKADW>>
	CHKADL			; Room for args to CHKADD
;	PUSH P,TCB		; Higher levels don't care
	PUSH P,PKT
	PUSH P,TPKT

; Top of main loop.  Get next packet to be processed.

INPRO0:	MOVE T1,TCPIPQ		; Get pointer to input queue head
;;;	NOSKED			; Interlock against Internet process
	LOAD PKT,QNEXT,(T1)	; Get pointer to first thing on queue
	CAIN PKT,0(T1)		; If that is the head itself
	  JRST INPROX		; Get out because it is empty (need OKSKED)
	SETSEC PKT,INTSEC	; Make extended address
	MOVE T1,PKT		; What to dequeue
	CALL DQ			; Remove from input queue
;;;	OKSKED
	SETZ TCB,		; May be a bad packet
	MOVX T1,PT%TDI		; TCP received packet
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes
	LOAD T1,PIDO,(PKT)	; Internet Data Offset
	XMOVEI TPKT,PKTELI(PKT)	; Pointer to Internet packet
	ADD TPKT,T1		; Pointer to TCP packet
	CALL TCPCKS		; Compute checksum function
	JUMPE T1,INPRO2		; Jump if good

; Packet has bad checksum.  Flush it.
;INPRO1:
	AOS BADPCT		; Count bad packets
	MOVX T1,PT%XX5		; Code for "Flushed by IP"
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes
	MOVX T1,PT%TKC		; TCP Killed due to bad checksum
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes
	CALL RETPKT		; Return the packet storage
	JRST INPRO0		; And hope for better luck next Pkt

; Packet is OK to process ...

INPRO2:
REPEAT 0,<
	SKIPN STATF		; Taking statistics right now?
	  JRST INPRO4		; No
	CALL ONLCLT		; Source is using our time base too?
	JUMPE T1,INPRO3		; Cannot process its timestamp if not.
	CALL GETTSO		; Get the timestamp from the option
	STOR T1,PTS,(PKT)	; Store in better part of packet
	MOVEI T1,IPDLAY		; Select delay histogram
	CALL TSTAMP		; Process and reset the timestamp
	JRST INPRO4

INPRO3:	MOVE T1,TODCLK		; Current millisecond
	STOR T1,PTS,(PKT)	; Make timestamp OK for subsequent chks
INPRO4:
> ; End of REPEAT 0

	CALL PKTEND		; Get seq. number of End of Pkt plus 1
	STOR T1,PESEQ,(PKT)	; Keep in handy place

; Now setup for a call to CHKADD which looks up the TCB addressed
; by the pkt.  If it is found, CHKADD calls INPUT with said TCB locked.

	MOVEI PARAMS,ARGBLK	; Arg area on stack (ref. via sec. 0)
	LOAD T1,PIDH,(PKT)	; Destination Host
	MOVEM T1,LH
	LOAD T2,PDP,(TPKT)	; Destination port
	MOVEM T2,LP
	LOAD T3,PISH,(PKT)	; Source Host
	MOVEM T3,FH
	LOAD T4,PSP,(TPKT)	; Source Port
	MOVEM T4,FP
	SETZM JCN		; No JCN.  Call is from IP.
	SETOM WILDOK		; Wild TCB (Listen) is OK for match
	MOVE T2,[MSEC1,,INPUT]	; Function to call if found
	MOVEM T2,FN
	SETZM ARG1		; No ARG1
	SETZM ARG2		; or ARG2

	MOVE T1,PARAMS		; Arg block for CHKADD
	CALL CHKADD		; Check the address of the packet

	CAMN T1,[-1]		; Packet disposed of successfully?
	  JRST INPRO0		; Yes. Do another one.

	PUSH P,T1
	MOVX T1,PT%XX5		; Code for "Flushed by IP"
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes
	POP P,T1

	JN PRST,(TPKT),INPRO5	; Don't respond to RST!
	HRRZ T2,T1		; Get just the error portion
	CAIE T2,EFP+↑D7		; Is it "No such TCB" ?
	 CAIN T2,ELP+↑D7	; Is it "No such TCB" ?
	  SETZ TCB,0		; Yes.  TCB used as flag to SNDRST
	CALL SNDRST		; Reply with an RST Packet
INPRO5:
; Done with packet.

	MOVX T1,PT%TID		; TCP done with packet
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes
	CALL RETPKT		; Give back the storage
	JRST INPRO0		; And process some more packets

; Packet queue completely processed.

INPROX:;OKSKED
	POP P,TPKT
	POP P,PKT
;	POP P,TCB
	CHKADR
	RET
; INPUT   2nd phase of InputProcessor.  Called via CHKADD from INPROC

;PKT/	(Extended) Internet Packet Pointer
;TPKT/	(Extended) TCP Packet Pointer
;TCB/	(Extended) Locked connection block
;
;	CALL INPUT
;Ret+1:	Always.  T1 has -1 if Pkt was handled OK,
;			 0 if Pkt was on a closed (NOTSYN,NOTSYN) connection,
;			-1,,EFP+↑D7


INPUT:	LOCAL <TESTB>
	LOAD T1,TSSYN,(TCB)	; State of Send synchronization
	LOAD T2,TRSYN,(TCB)	; State of Recv synchronization
	CAIN T1,NOTSYN
	 CAIE T2,NOTSYN
	  CAIA			; Still open in some respect
	   JRST INPUTF		; Packet on closed connection.

	CALL CHKSEQ		; See if properly sequenced
	MOVEM T1,TESTB		; -1 OK or DUP,,0; IGN,,0; ERR,,EFP+↑D7
				;		   (ReSync)
	HLRZ T2,TESTB
	CAMN TESTB,[-1]		; TRUE ?
	  JRST INPUT3
	CAIN T2,ERR		; ERROR ?
	  JRST INPUT4

; Packet is a DUP, IGN, (or "other")

;	CAIE T2,IGN		; IGNORABLE?
	 CAIE T2,DUP		; DUPLICATE ?
	  JRST INPUT2		; NOTA -- Ignore it

; Duplicate received. (SYN on a synched connection)

	AOS DUPKCT		; Count it.
	JE PACK,(TPKT),INPUT1	; No ACK.  Forget the following.
	JN PRST,(TPKT),INPUT1	; Don't process ACK from RST packet
	LOAD T1,PACKS,(TPKT)	; ACK sequence from Packet
	CALL PRCACK		; Process the ACK
INPUT1:

	CALL NULPKT		; Does the packet have any contents
	SKIPN T1		; Skip FRCPKT if null
	  CALL FRCPKT		; Generate an ACK for the DUP
	SKIPA T1,[PT%TKD]

; All packets except ERR and good ones come here (ie DUP & IGN)
; Pkt should be IGNored, resyncing connection

INPUT2:	  MOVX T1,PT%TKR	; TCP Killed, Resyncing
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes
	MOVX T1,PT%XX6		; IP code
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes
	CALL RETPKT		; Release the storage space
	JRST INPUTT		; Return true to say it's been handled



; Handle normal, acceptable packet

INPUT3:	LOAD T1,PIDH,(PKT)	; Get which incarnation he knows us under
	STOR T1,TLH,(TCB)	; Stick it away
	CALL PRCPKT		; Process the packet
	AOS IPPKCT		; Count as Processed by IP
	JRST INPUTT		; and return TRUE



; CHKSEQ said this packet is in error -- no TCB (ERR,,EFP+↑D7)

INPUT4:	MOVX T1,PT%XX3		; IP code
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes
	MOVX T1,PT%TKX		; TCP Killed, no TCB
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes
	HRRO T1,TESTB		; -1,,EFP+↑D7 to return
	EXIT INPUTX		; (must RETPKT)



; Packet on closed connection (NOTSYN,NOTSYN)

INPUTF:	MOVX T1,PT%TKN		; TCP Pkt received on closed connection
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes
	TDZA T1,T1		; Packet not processed (must RETPKT)
INPUTT:	  SETO T1,		; Return TRUE, packet processed & RETPKT'd
INPUTX:	RESTORE
	RET

; CHKSEQ	Determine the validity of a packet on the basis of
;		sequence number, acknowledge number, synchronization
;		state, and the presence of SYN in the packet.

;PKT/	(Extended) Internet Packet Pointer
;TPKT/	(Extended) TCP Packet Pointer
;TCB/	(Extended) Locked connection block
;
;	CALL CHKSEQ
;Ret+1:	always with T1 having ERR,,EFP+↑D7; DUP,,0; IGN,,0 or -1 if OK


CHKSEQ:	LOAD T1,TRSYN,(TCB)	; Recv state
	CAIN T1,SYNABL		; Listening?
	  JRST CKACKS		; Yes.  Validate using ACK sequence
	; Fall into CKSYNS
	; Fall into CKSYNS from CHKSEQ above

; CKSYNS	Check sequence number.  Used while receive sync is
;		established.  A packet sequenced within the receive
;		window is accepted; without is considered a duplicate.

; Stray SYNs on already synched connections are called DUPlicates
; and will cause a null packet to be emitted which contains the
; sequence we are sending on and the ACK sequence describing what we
; want to hear next.  This is enough information for the other end to
; be able to form an RESET packet which will flush this connection.  He
; would do this only if he had restarted recently.

;PKT/	(Extended) Internet Packet Pointer
;TPKT/	(Extended) TCP Packet Pointer
;TCB/	(Extended) Locked connection block
;
;	CALL CKSYNS
;Ret+1:	always. T1 has -1 if pkt is OK to process, or DUP,,0; IGN,,0

CKSYNS:	JE PSYN,(TPKT),CKSYN1	; Jump if no stray SYN in packet.
	LOAD T1,TRSYN,(TCB)	; Get receive state
	LOAD T2,PSEQ,(TPKT)	; and sequence number from packet
	LOAD T3,TRIS,(TCB)	; and seq. number of original SYN recvd
	CAIN T1,SYNRCV		; In SYN-RECEIVED?
	 CAMN T2,T3		; or just another copy of orig SYN?
	  JRST CKSYND		; Call it a dup. to get null pkt sent
	CALL SYNAGN		; Resync. the conn.  Other end crashed
	MOVX T1,<IGN,,0>	; Tell caller to ignore this packet
	EXIT CKSYNX

CKSYN1:	LOAD T1,TRLFT,(TCB)	; Left window edge
	LOAD T2,TRWND,(TCB)	; Width of window
	JE PRST,(TPKT),CKSYN2	; No window diddle if not RST in pkt
	SKIPN T3,T2		; If non-0, use it
	  MOVX T3,1		; Otherwise diddle so RST gets done
	ADD T3,T1		; Compute Right plus 1 or width 1 window
	MODSEQ T3		; Keep within right number of bits
	LOAD T2,PSEQ,(TPKT)	; Get sequence number of packet
	CALL CHKWND		; Is RST within the window?
	JRST CKSYN9		; Go see

CKSYN2:	ADD T2,T1		; Right window edge plus 1
	MODSEQ T2
	LOAD T3,PSEQ,(TPKT)	; Packet sequence number
	LOAD T4,PESEQ,(PKT)	; Sequence number following Pkt
	CALL OVRLAP		; Pkt and window have common point(s)?
CKSYN9:	SKIPN T1		; Skip if yes
CKSYND:	  MOVX T1,<DUP,,0>	; Call it a DUPlicate
CKSYNX:	RET			; From CHKSEQ

; CKACKS	Check ACK Sequence.  Validate pkt when not synchronized.

; When receive synchronization has not been established, the only
; acceptable packet is one which will establish receive synchronization.
; Furthermore, if we have established send synchronization, the packet,
; if it acknowledges anything, must acknowledge something we have
; currently sent (eg, a SYN).

;PKT/	(Extended) Internet Packet Pointer
;TPKT/	(Extended) TCP Packet Pointer
;TCB/	(Extended) Locked connection block
;
;	CALL CKACKS
;Ret+1:	always. T1 having ERR,,EFP+↑D7 else -1 if pkt is ok to process

CKACKS:	JN PACK,(TPKT),CKASE1	; Jump if packet ACKs something
	JE PSYN,(TPKT),CKASE2	; Give error if no SYN and no ACK
	JRST CKASET		; SYN and no ACK.  Try to open conn.

CKASE1:	LOAD T1,TSSYN,(TCB)	; Get send state
	CAIN T1,SYNABL		; Have a Send Sequence to check?
	  JRST CKASE2		; Error if not waiting for SYN
				; (ACKing something we never sent)
	LOAD T1,TSLFT,(TCB)	; Get Send Left
	LOAD T2,PACKS,(TPKT)	; What the Packet ACKS
	LOAD T3,TSSEQ,(TCB)	; Current Send Sequence
	ADDI T3,1
	MODSEQ T3
	CALL CHKWND		; Does Pkt ACK someting outstanding?
	JUMPN T1,CKASET		; Return TRUE if so
CKASE2:
	CALL RSTADR		; Restore Wild foreign address fields
	SKIPA T1,[ERR,,EFP+↑D7]	; Give error
CKASET:	  SETO T1,		; Ok to process
	RET			; From CHKSEQ

; PRCPKT   Process Packet which has been determined to be acceptable

;PKT/	(Extended) Internet Packet Pointer
;TPKT/	(Extended) TCP Packet Pointer
;TCB/	(Extended) Locked connection block
;
;	CALL PRCPKT
;Ret+1:	always. No value returned. PKT disposed of

PRCPKT:	LOCAL <QP,QS>		; OLDR??

; Extract IP & TCP options

	CALL TCPXIO		; Extract IP
	CALL TCPXTO		; and TCP options

; Process RESET

	JE PRST,(TPKT),PRCPK1	; Jump if not a RESET Packet
	CALL PRCRST		; Process the reset (may not have PACK)
	MOVX T1,PT%TRS
	JRST PRCPKR		; Return to caller.
PRCPK1:

; Process ACK

	JE PACK,(TPKT),PRCPK2	; Does the packet acknowledge anything?
	LOAD T1,PACKS,(TPKT)	; Yes.  Get the ack sequence
	CALL PRCACK		; And process the ACK & update send window
PRCPK2:

; Process Urgent

	JE PURG,(TPKT),PRCPK5	; Contains urgent pointer?
	CALL PRCURG		; Yes.  Process that.
PRCPK5:

; See if there is anything to process in the packet
; If not return the storage and return to caller.

	CALL NULPKT		; See if PKT is null
	MOVX T2,PT%TID
	EXCH T1,T2
	JUMPN T2,PRCPKR		; Jump if so.  Return to caller.

; Process SYN

	JE PSYN,(TPKT),PRCPK7	; Jump if no SYN
	CALL PRCSYN		; Process the SYN
PRCPK7:				; Sets TRLFT at TRIS+1, R-state changed
				; Tell USREVT(OK) if SYN.SYN
; Trim input packet if short on space

	CALL TRMPKT		; Trim it size or flush PKT for space
	JUMPE PKT,PRCPKX

; Queue the Packet for the Reassembler.  The receive packet queue is
; basically ordered by sequence number, but may have partially
; overlapping segments on it.   (try searching right to left)

	XMOVEI QS,TCBRPQ(TCB)	; Set scan pointer to Reassembly Q head
	JE TRPP,(TCB),PRCPK8	; Partial Pkt contains left. Skip it.
	LOAD QS,QNEXT,(QS)	; Get next packet after the partial one.
	SETSEC QS,INTSEC	; Make extended address

; Top of the search for right place to insert loop

PRCPK8:	LOAD QS,QNEXT,(QS)	; Get ptr to thing after scan pointer
	CAIN QS,TCBRPQ(TCB)	; If that is the head, all has been seen
	  JRST PRCPK9		; So insert just before the head (= end)
	SETSEC QS,INTSEC	; Make extended address
	LOAD T3,PIDO,(QS)	; Internet data offset
	XMOVEI T4,PKTELI(QS)	; Pointer to Internet portion
	ADD T4,T3		; Pointer to TCP portion
	LOAD T1,TRLFT,(TCB)	; Get Recv Left
	LOAD T2,PSEQ,(TPKT)	; Sequence of new packet
	LOAD T3,PSEQ,(T4)	; Sequence of current pkt on the queue
	PUSH P,T3		; Save sequence number around call
	CALL CHKWND		; New pkt fits before this one on queue?
	POP P,T3		; Recover sequence number for use below
	JUMPE T1,PRCPK8		; No. Advance queue scan ptr. Try next.

; Have a likely place to put the packet.  Be sure that we will not
; insert before one which contains left.

	MOVE T1,T3		; Sequence number of packet on queue
	LOAD T2,TRLFT,(TCB)	; Recv Left
	LOAD T3,PESEQ,(QS)	; End of packet + 1
	CALL CHKWND		; Left within this packet?
	JUMPN T1,PRCPK8		; Jump if so.  Look at next packet.

; Now QS points to where to insert the new packet (before QS).  Do
; checking to see if the packet to the left and packets to the
; right are completely contained by the packet being processed.
; Release storage of those which are.  This works well if retranmissions
; are equal to- or bigger than- the original transmissions.
; It does not worry about duplicates of (small) original transmissions
; which might be contained by packets already queued.
; N.B.  It works to replace a "partial packet" with a bigger one.


PRCPK9:	SETSEC QS,INTSEC	; Make extended address
	LOAD T1,QPREV,(QS)	; Get one just skipped (or header)
	CAIE T1,TCBRPQ(TCB)	; Avoid fiddling with the header
	  CALL REMCHK		; Check and maybe remove T1 from Q
				; (REMCHK does SETSEC)
PRCP9A:	HRRZ T1,QS		; Get current insert point
	CAIN T1,TCBRPQ(TCB)	; Is that the header?
	  JRST PRCP10		; Yes.  Don't check that.
	MOVE QP,QS		; Save as predecessor in case needed
	LOAD QS,QNEXT,(QP)	; Get next item on Q for next time
	SETSEC QS,INTSEC	; Make extended address
	MOVE T1,QP		; Check the current item
	CALL REMCHK		; Maybe delete it
				; (REMCHK does SETSEC)
	JUMPN T1,PRCP9A		; Jump if deleted (QS ok for next time)
	MOVE QS,QP		; Restore QS (point of insertion)
PRCP10:

; Actually queue the packet for the Reassembler.

	MOVE T1,PKT		; Select the packet for EnQueueing
	MOVE T2,QS		; Where to enqueue it -- before QS.
	CALL NQ			; Enqueue the packet
	MOVX T1,PT%XX3		; Code for "processed by IP"
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes
	MOVX T1,PT%TQR
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes
; Now see if the Reassembler has anything to do.  Call it if so.

	LOAD T4,QNEXT,<+TCBRPQ(TCB)> ; Must be a pkt queued!
	SETSEC T4,INTSEC	; Make extended address
	LOAD T2,PESEQ,(T4)	; Fetch end sequence for what follows
	LOAD T3,PIDO,(T4)	; Internet data offset
	XMOVEI T4,PKTELI(T4)	; Pointer to Internet portion
	ADD T4,T3		; Pointer to TCP portion
	LOAD T3,PCTL,(T4)	; Get word containing control bits
	TXNE T3,<PSYN!PFIN>	; Some kind of control?
	  JRST PRCP11		; Yes.  RA must process it now.
	JN TRCB,(TCB),PRCP11	; Maybe there is a partially filled buffer
	JN TTVT,(TCB),PRCP11	; No normal buffers for TVTs
	LOAD T1,QNEXT,<+TCBRBQ(TCB)> ; Pointer 1st buffer on queue
	CAIN T1,TCBRBQ(TCB)	; Empty?
	  JRST PRCPKX		; Yes.  RA cannot do anything.
PRCP11:	MOVE T3,T2		; Recover end sequence
	LOAD T1,PSYN,(TPKT)	; If have a SYN, TRLFT already increased
	ADD T3,T1		; so increase right too
	MODSEQ T3
	LOAD T1,PSEQ,(T4)	; Get sequence number of pkt
	LOAD T2,TRLFT,(TCB)	; Recv Left is the point of reassembly
	CALL CHKWND		; Did this packet fill the hole?
	JUMPE T1,PRCPKX		; Jump if not.  No need to run RA.
	$SIGNL(RA,0)		; Make Reassembler run now
	EXIT PRCPKX

; Here with packet which cannot be processed further.  Release storage.

PRCPKR:	TDNE T1,INTTRC		; Want trace? (PT%TID,PT%TRS)
	  CALL PRNPKT		; Yes
	MOVX T1,PT%XX6		; "Flushed by IP" code
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes, Call Packet Printer
	CALL RETPKT		; Give back to free storage

PRCPKX:	RESTORE
	RET

; REMCHK	Check whether incoming packet contains a packet on a
;		reassembly queue.  If so, delete the Q'd packet.

;T1/	NOT EXTENDED pointer to packet on a reassembly queue
;PKT/	(Extended) Internet Packet Pointer (incoming pkt)
;TPKT/	(Extended) TCP Packet Pointer (incoming pkt)
;TCB/	(Extended) Locked connection block
;
;	CALL REMCHK
;Ret+1:	Always.  T1 -1 if packet was deleted or 0 otherwise.


REMCHK:	LOCAL <QPKT>		; Holds pointer to queued packet
	MOVEM T1,QPKT
	SETSEC QPKT,INTSEC	; Make extended address
	LOAD T3,PIDO,(QPKT)	; Number of words of IN header
	XMOVEI T4,PKTELI(QPKT)	; Pointer to Internet portion
	ADD T4,T3		; Pointer to TCP portion
	LOAD T1,PSEQ,(T4)	; Start of queued packet
	LOAD T2,PESEQ,(QPKT)	; End + 1 of queued packet
	LOAD T3,PESEQ,(PKT)	; End + 1 of Pkt being processed
	LOAD T4,PSEQ,(TPKT)	; Start of packet being processed
	CAMN T1,T4		; Quick check for exact duplicate
	 CAME T2,T3		; (Faster than OVRLAP)
	  CAIA			; Have to use OVRLAP to be sure
	   TDZA T1,T1		; Fake false return from OVRLAP
	    CALL OVRLAP		; See if QPKT has something PKT does not
	JUMPN T1,REMCH9		; Jump if so.  Must keep both.

	MOVE T1,QPKT		; This packet is extra baggage.
	CALL DQ			; Remove it from the queue.
	PUSH P,PKT

	MOVE PKT,T1		; Put pointer in standard place
	MOVX T1,PT%TDR
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes
	CALL RETPKT		; Give up possibly full-size packet

	POP P,PKT
	SKIPA T1,[-1]		; Return true to say something was done
REMCH9:	  SETZ T1,		; Return false to say nothing was done
	RESTORE
	RET

; PRCRST	Process a RESET packet

;PKT/	(Extended) Internet Packet Pointer
;TPKT/	(Extended) TCP Packet Pointer
;TCB/	(Extended) Locked connection block
;
;	CALL PRCRST
;Ret+1:	always


PRCRST:	AOS RSTRCT		; Count RSTs received
	LOAD T1,TSSYN,(TCB)	; Send state
	LOAD T2,TRSYN,(TCB)	; Recv state
	CAIN T1,FINSNT
	 CAIE T2,NOTSYN
	  JRST PRCRS1

; S-FINSNT, R-NOTSYN		; PKT always have ACK??
	LOAD T1,PACKS,(TPKT)	; ACK Sequence from packet
	CALL PRCACK		; Process it.
	EXIT PRCRSX

PRCRS1: JRST PRCRS2	; prevent hanging when SYNAGN forgets user open timeout
	JN TSOPN,(TCB),PRCRS2	; Jump if user thinks connection is open
	JE TSPRS,(TCB),PRCRS2	; Give error if not persistent
	CALL SYNAGN		; Start over.
	EXIT PRCRSX

PRCRS2:	MOVX T1,EFP+↑D7		; "Connection RESET"
	CALL USRERR		; Tell user.
	MOVX T1,EFP+↑D7		; "Connection RESET"
	CALL ABTCON		; Abort the connection.  Flush bufs etc

PRCRSX:	RET
; PRCWND	Process Window Information from incoming packet

; We desire the most recently sent information to be that which is acted
; on.  Since retransmitted packets have more current information than
; when they were originally transmitted, the packet sequence is not a
; good basis for deciding if a given packet has more recent info.  In
; order to prevent lockups, window information must be processed out of
; sequence.

;(PKT/	(Extended) Internet Packet Pointer)
;TPKT/	(Extended) TCP Packet Pointer
;TCB/	(Extended) Locked connection block
;
;	CALL PRCWND
;Ret+1:	always.

PRCWND:	LOAD T1,TSWND,(TCB)	; The current window
	LOAD T2,PWNDO,(TPKT)	; The new window
	STOR T2,TSWND,(TCB)	; Set into the TCB
	JUMPN T1,PRCWNX		; Exit if window not closed previously
	JUMPE T2,PRCWNX		; Or not now open

; Window opened

	LOAD T3,PACKS,(TPKT)	; Get the ACK Sequence from the packet
	LOAD T4,TSLFT,(TCB)	; and Send Left from TCB
	CAME T3,T4		; Re-request for Left?
	  EXIT PRCWNX		; No.

	LOAD T1,QNEXT,<+TCBRXQ(TCB)>
	CAIN T1,TCBRXQ(TCB)	; Retransmit queue empty?
	  EXIT PRCWNX		; Yes.  No need for retranmitter to run

	$SIGNL(RX,0)		; Make Retransmitter run now

PRCWNX:	RET

; PRCACK	Delete acknowledged send data

; Called from IP while processing incoming packets and by BG if it
; needs to fake an ACK for a FIN.

;T1/	ACKnowledge sequence
;TPKT/	(Extended) TCP Packet Pointer (for PRCWND)
;TCB/	(Extended) Locked Connection block
;
;	CALL PRCACK
;Ret+1:	always

PRCACK::LOCAL <ACKSEQ,LEFT,OLDR>
	MOVEM T1,ACKSEQ		; Save in good place
	LOAD T2,TSLFT,(TCB)	; Send Left
	MOVEM T2,LEFT		; Save in safe place
	EXCH T1,T2		; Put in desired ACs for CHKWND below
	LOAD T3,TSSEQ,(TCB)	; Get the current Send Sequence
	ADDI T3,1
	MODSEQ T3
	CALL CHKWND		; ACKs something outstanding?
	JUMPE T1,PRCACX		; Do no more if not

; Process Urgent

	JE TSURG,(TCB),PRCAC0	; Skip following if not in urgent send mode
	MOVE T1,LEFT		; Send left
	LOAD T2,TSURP,(TCB)	; Send urgent pointer
	SUBI T2,1		; Consider ACKd if ACK=URP
	MODSEQ T2		; Worry about 32-bit arithmetic
	MOVE T3,ACKSEQ		; Number being acknowledged
	CALL CHKWND		; See if URP is being ACKd
	JUMPE T1,PRCAC0		; Jump if not
	SETZRO TSURG,(TCB)	; Leave urgent send mode
PRCAC0:

; Process window

	MOVE OLDR,LEFT		; Old Send Left
	LOAD T1,TSWND,(TCB)	; Old Send Window
	ADD OLDR,T1		; Compute Old Send Right
	MODSEQ OLDR
	CALL PRCWND		; Process Window info in packet
				; (TSWND = PWNDO, maybe RX)

	STOR ACKSEQ,TSLFT,(TCB)	; ACK Sequence is our Send Left

; Process unACKed SYN

	LOAD T1,TSSYN,(TCB)	; Send State
	CAIE T1,SYNSNT
	  JRST PRCAC2		; No unACKd SYN to handle

	MOVX T1,SYNCED
	STOR T1,TSSYN,(TCB)	; Set fully synched state on send side
	LOAD T2,TRSYN,(TCB)	; Recv state
	CAIE T2,SYNRCV
	  JRST PRCAC1
	STOR T1,TRSYN,(TCB)	; Make receive side open too

	JN TTVT,(TCB),PRCA0A	; Avoid RA since TVT not assigned
	$SIGNL(RA,0)		; Make RA remove any dangling SYN pkt
PRCA0A:

	MOVX T1,OK		; General success code
	CALL USREVT		; Pass the event to the user
PRCAC1:				; (No TVT error needs work here)

	JE TRPP,(TCB),PRCAC2	; If there is no partial pkt in RA
	$SIGNL(RA,0)		; Make Reassembler run now
PRCAC2:

	STOR ACKSEQ,TSLFT,(TCB)	; ACK Sequence is our Send Left

	MOVEI T1,TCBRXQ(TCB)	; Retransmit queue
	SETSEC T1,INTSEC	; Make extended address
	MOVE T2,LEFT		; Old Left
	MOVE T3,ACKSEQ		; New Left
	SETZ T4,		; A send queue is being processed
	CALL REMSEQ		; Delete ACKed packets

; Now that the new Send Window location and extent have been set,
; determine if the Packetizer should be started.  This means there
; must be something waiting to be sent and window space to send it
; in and the connection state must be right.

;PRCAC3:
	LOAD T1,TSSYN,(TCB)	; Get send state
	CAIE T1,SYNCED		; Fully synchronized?
	  JRST PRCAC4		; No.  Don't start PZ

; Test for waiting output on TCP Virtual Terminal connection

	JE TTVT,(TCB),PRCA34	; Jump if not a TVT
	LOAD T2,TVTL,(TCB)	; Get the line number of the TVt
	JUMPE T2,PRCAC4		; Jump if not assigned yet
	CALL LCKTTY		; Lock TTY & NOINT
	  JUMPLE T2,PRCA33	; Inactive or becoming active

	PUSH P,T2		; Save the line block addr
	CALL TVTOSP		; Find out if anything waiting to go
	POP P,T2		; Recover addr of line block

	SKIPA
PRCA33:	  MOVX T1,0		; No TTY means no characters

	PUSH P,T1		; Save the count
	CALL ULKTTY		; Unlock TTY & OKINT
	POP P,T1		; Get back the count

	JUMPG T1,PRCA35		; Something to be sent.  See if OK
	JRST PRCAC4		; Nothing to be sent.
PRCA34:

; Test for waiting output on a normal TCP data connection

	JN TSCB,(TCB),PRCA35	; Jump if something wait to be sent
	LOAD T1,QNEXT,<+TCBSBQ(TCB)>
	CAIN T1,TCBSBQ(TCB)	; Any queued from user?
	  JRST PRCAC4		; Nothing to be sent
PRCA35:

; Connection state is right and there is something waiting to be sent

	LOAD T1,TSLFT,(TCB)	; Get new Send Left
	LOAD T2,TSSEQ,(TCB)	; Get current Send Sequence
	SUB T2,T1		; Bytes used in send window
	MODSEQ T2		; (Must be .ge. 0)
	LSH T2,2		; 4 * Used
	LOAD T3,TSWND,(TCB)	; Get new Send Window
	IMULI T3,3		; 3 * Offered window
	CAMGE T2,T3		; If Used/Offered .lt. 3/4
	  CALL FRCPKT		; Run PZ, but after RA
PRCAC4:

; See if packets which might have been made untransmittable due to
; Send Right moving backwards have now become transmittable.  If so
; start the Retransmitter.  This is due to the "PUSH problem"
; in which the sender has no idea of the size of the receive
; buffers and therefore cannot tell how many sequence number slots
; a PUSH will absorb.

	LOAD T1,QNEXT,<+TCBRXQ(TCB)>
	CAIN T1,TCBRXQ(TCB)	; Any packets on retransmit queue?
	  JRST PRCACX		; No
	MOVE T1,LEFT		; Old Left before ACK
	MOVE T2,OLDR		; Old right, before this ACK
	LOAD T3,TSSEQ,(TCB)	; Current send sequence
	CALL CHKWND		; Any pkts cutoff?
	JUMPE T1,PRCACX		; Jump if not
	MOVE T1,OLDR		; Right before this ACK,WND processed
	MOVE T2,ACKSEQ		; New Left due to this ACK
	LOAD T3,TSSEQ,(TCB)	; Current send sequence
	CALL CHKWND		; See if ACK has exposed any pkts
	JUMPE T1,PRCACX		; Jump if not
	XMOVEI T1,RX		; What to signal -- the retransmitter
	MOVX T2,0		; When to run it -- now
	CALL SIGNAL		; But after we finish here

PRCACX:	RESTORE
	RET

; PRCURG	Process URGENT pointer from packet

;TCB/	(Extended) pointer to locked connection block
;(PKT/	(Extended) pointer to packet)
;TPKT/	(Extended) pointer to TCP portion of packet
;
;	CALL PRCURG
;Ret+1:	Always.

PRCURG:	LOCAL <URGPTR>
	LOAD URGPTR,PSEQ,(TPKT)	; Sequence number of packet
	LOAD T1,PURGP,(TPKT)	; Offset to urgent pointer
	ADD URGPTR,T1		; Compute actual urgent pointer
	MODSEQ URGPTR		; Reduce to the right number of bits
	JN TRURG,(TCB),PRCUR1	; Already in urgent receive mode?
	STOR URGPTR,TRURP,(TCB)	; No.  Set receive urgent pointer
	SETONE TRURG,(TCB)	; Mark it as valid.
	CALL USRURG		; Signal user of urgent data waiting
	EXIT PRCURX

PRCUR1:	LOAD T1,TRLFT,(TCB)	; Receive Left pointer
	MOVE T2,URGPTR		; What packet says pointer is
	LOAD T3,TRURP,(TCB)	; Current Urgent pointer
	CALL CHKWND		; See if urgent pointer is "bigger"
	JUMPN T1,PRCURX		; Nothing to do if not
	STOR URGPTR,TRURP,(TCB)	; Update receive urgent pointer
PRCURX:	RESTORE
	RET
; PRCSYN	Process SYN in incoming packet

;PKT/	(Extended) Internet Packet Pointer
;TPKT/	(Extended) TCP Packet Pointer
;TCB/	(Extended) Locked connection block
;
;	CALL PRCSYN
;Ret+1:	always

PRCSYN:	LOAD T1,TRSYN,(TCB)	; Get receive state
	CAIN T1,FINRCV		; Ignore SYN if FIN Received
	  EXIT PRCSYX
	LOAD T2,TSSYN,(TCB)	; Get send state
	CAIE T2,SYNCED		; Send sync established?
	  JRST PRCSY2		; No.
; S-SYNCED (Recv'd ACK, maybe this packet)
	MOVX T1,SYNCED
	STOR T1,TRSYN,(TCB)	; Make the state "synchronized" (syn.syn)
	MOVX T1,OK		; General success event code
	CALL USREVT		; Tell the user connection is open now
	JRST PRCSY3
; S-not synced (& not in this packet)
PRCSY2:	MOVX T1,SYNRCV		; SYN Received state
	STOR T1,TRSYN,(TCB)	; is new Receive state
	LOAD T2,PWNDO,(TPKT)	; Extract the window
	STOR T2,TSWND,(TCB)	; That is the (first) send window for us
PRCSY3:
	LOAD T1,PSEQ,(TPKT)	; Get the packet sequence number
	STOR T1,TRIS,(TCB)	; Save for filtering duplicate SYNs
	STOR T1,TRURP,(TCB)	; Not in urgent receive mode
	ADDI T1,1		; Advance Recv.Left over SYN
	MODSEQ T1
	STOR T1,TRLFT,(TCB)	; That is the first Left for us

; If the Reassembler will not see this packet, get an ACK for it now.
; Otherwise, see to it that one is eventually generated.

	LOAD T1,PIPL,(PKT)	; Total Internet packet length in octets
	LOAD T2,PIDO,(PKT)	; Data offset in 32-bit words
	LOAD T3,PTDO,(TPKT)	; TCP data offset in 32-bit words
	ADD T2,T3		; Compute total header length
	ASH T2,2		; In bytes
	CAMLE T1,T2		; Is there any data in TCP portion?
	  JRST PRCSY4		; Yes.  RA must see it.
	LOAD T1,PCTL,(TPKT)	; Get word containing control flags
	TXNE T1,<PFIN!PEOL>
	  JRST PRCSY4		; RA must see these
	CALL FRCPKT		; Force an ACK, now.
	JRST PRCSY5

PRCSY4:	MOVE T1,TCPRA0		; Time to wait for RA
	CALL ENCPKT		; Encourage an ACK in the future
PRCSY5:	AOS SYNRCT		; Count SYNs received
PRCSYX:	RET
; SNDRST	Send a RESET Response to the Foreign TCP

;PKT/	(Extended) Internet Packet Pointer
;TPKT/	(Extended) TCP Packet Pointer
;TCB/	0 or (Extended) connection block
;
;	CALL SNDRST		Beware of routing options
;Ret+1:	always

SNDRST:	STACKL <<ADR,4>>
	LOCAL <PKTACK,ENDPKT>
	PUSH P,TCB
	PUSH P,PKT
	PUSH P,TPKT

	LOAD T1,PISH,(PKT)	; Extract source of packet
	LOAD T2,PIDH,(PKT)	; Get number he knew me by
	LOAD T3,PSP,(TPKT)
	LOAD T4,PDP,(TPKT)	; and destination port
	DMOVEM T1,ADR		; Swap into address block
	DMOVEM T3,2+ADR
	LOAD ENDPKT,PESEQ,(PKT)	; Get the end of the packet (plus 1)
	LOAD T1,PACKS,(TPKT)	; Extract the ACK Sequence from PKT
	MOVEM T1,PKTACK

; Now we have tucked away all we need from the incoming packet.

	SETZB T1,TCB		; Min packet & no data & no TCB
	XMOVEI T2,ADR		; Address block
	CALL TCPIPK		; Get packet & initialize header
	  JRST SNDRSX		; Error.  Other end will try again.

	SETONE PRST,(TPKT)	; Set the RST bit
	STOR ENDPKT,PACKS,(TPKT); Arrange to ACK all of the input packet
	SETONE PACK,(TPKT)	; Set the ACK bit
	MOVE T1,PKTACK		; ACK Sequence from packet
	JUMPE TCB,SNDRS1	; if there is no TCB
	LOAD T1,TSSEQ,(TCB)	; Else use the right thing
SNDRS1:
	STOR T1,PSEQ,(TPKT)	; As the Packet Sequence number

	MOVE T1,TODCLK		; "Now"
	STOR T1,PTG,(PKT)	; Store as Time Generated
	CALL TCPCKS		; Compute TCP packet checksum
	STOR T1,PTCKS,(TPKT)	; Set into packet

	MOVX T1,PT%XX2		; Fake OP
	SKIPN TCB
	  MOVX T1,PT%XX7
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes, Call the packet printer
	MOVX T1,PT%TIR
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes
;SNDRS3:
	AOS RSTSCT		; Count errors sent
	AOS PZPKCT		; Count Packetized packets
	AOS OPPKCT		; Count Output packets
	XMOVEI T1,OPDLAY	; Select OP Delay Histogram
	SKIPE STATF		; Avoid overhead if not taking stats
	  CALL TSTAMP		; Process the timestamp

	CALL SNDGAT		; Send it to gateway. (NB: PPROG is 0)
SNDRSX:	POP P,TPKT
	POP P,PKT
	POP P,TCB
	RESTORE
	RET

; REMSEQ    Remove packets from a queue which are between Left and Right

;T1/	(Extended) Queue head pointer
;T2/	Left
;T3/	Right
;T4/	Receive Queue Flag (0 is SEND is TCBRXQ)
;TCB/	(Extended) Locked connection block
;
;	CALL REMSEQ
;Ret+1:	always

REMSEQ::STACKL <NEXTRX,RECVF>
	LOCAL <Q,LEFT,RIGHT,NEXT>
	PUSH P,PKT
	PUSH P,TPKT
	DMOVEM T1,Q		; T1,T2 to Q,LEFT
	MOVEM T3,RIGHT		; T3 to RIGHT
	MOVEM T4,RECVF

	HRLOI T1,377777		; Infinity
	MOVEM T1,NEXTRX		; is first quess at next RX time
	LOAD NEXT,QNEXT,(Q)	; Get first thing on Queue (if any)
	SETSEC NEXT,INTSEC	; Make extended address

REMSE1:	MOVE PKT,NEXT		; Get the current pkt to standard place
	LOAD NEXT,QNEXT,(PKT)	; Set for next time
	SETSEC NEXT,INTSEC	; Make extended address
	CAMN PKT,Q		; Is this the queue Head?
	  JRST REMSE7		; Yes. Done.  Whole queue scanned.
	MOVE T1,LEFT
	LOAD T2,PESEQ,(PKT)	; End of packet plus one
	SUBI T2,1		; Seq. Num of last byte in packet
	MODSEQ T2
	MOVE T3,RIGHT
	CALL CHKWND		; Is end of packet within the window?
	JUMPN T1,REMSE2		; Yes.  Go delete the packet.
	LOAD T1,PXT,(PKT)	; Get the Transmit time
	LOAD T2,PRXI,(PKT)	; and current Retransmit interval
	ADD T1,T2		; Time of next retransmit
	CAMG T1,NEXTRX		; MIN with last time of next RX
	MOVEM T1,NEXTRX
	JRST REMSE1		; Continue scanning the queue

REMSE2:	MOVE T1,PKT
	CALL DQ			; Dequeue the packet from the queue
	SKIPE RECVF		; Processing receive packet queue?
	  JRST REMSE6		; Yes

; Send Queue

	LOAD T1,PIDO,(PKT)	; Number of words in the IN header
	XMOVEI TPKT,PKTELI(PKT)	; Pointer to Internet portion
	ADD TPKT,T1		; Pointer to TCP portion

; ACK of FIN

	JE PFIN,(TPKT),REMSE4	; Skip this part if not ACK of FIN
	MOVX T1,NOTSYN		; Not Synchronized state (dead)
	STOR T1,TSSYN,(TCB)	; Set into TCB
	LOAD T1,TRSYN,(TCB)	; Get receive state
	CAIE T1,NOTSYN		; Also closed?
	  JRST REMSE4
	SKIPE INTSCR		; Running in Secure mode?
	  CALL SCRCLS		; Yes.  Send a Secure Close Option
	MOVX T1,XLP+↑D3		; "CLOSED" event code
	CALL USREVT		; Pass the word to the user
REMSE4:

; Update estimate of round trip time

; Instead of discarding RX'd packets, include them in the sum...
; If the original is not being ACKed, then a packet was lost so
; something is probably overloaded, slow down RX in either case
;	JN PRXD,(PKT),REMSE6	; No statistics on RX'd packets

	MOVE T1,TODCLK		; "Now"
	LOAD T2,PTG,(PKT)	; Time packet was originally generated
	SUB T1,T2		; How long it took to ACK it

	JE <TRXPN,TRXPD>,(TCB),REMSE5 ; Newer algorithm

	LOAD T2,TMNRT,(TCB)	; Min Round Trip time
	CAMGE T1,T2		; Is this one shorter?
	  STOR T1,TMNRT,(TCB)	; Save new min
	LOAD T2,TMXRT,(TCB)	; MAX Round Trip time
	CAMLE T1,T2		; Is this longer?
	  STOR T1,TMXRT,(TCB)	; Save new max
	JRST REMSE6

REMSE5:	LOAD T4,TSMRT,(TCB)	; Current estimate
	MOVE T3,T4
	LSH T3,2
	CAMLE T1,T3		; If packet time is greater than
	  JFCL ;JRST REMSE6		; 4*current estimate, discard it
	MOVX T2,1
	MOVNI T3,@TCPRXF	; Positive scale factor
	LSH T2,(T3)		; Scaled 1.0
	SUB T2,TCPRXS		; Scaled (1-alpha)
	IMUL T1,T2		; RTT*(1-alpha)
	IMUL T4,TCPRXS		; SRTT*alpha
	ADD T1,T4
	LSH T1,@TCPRXF		; New estimate of round trip time
	STOR T1,TSMRT,(TCB)
REMSE6:

; Both Send and Receive Queues

	SETZRO PPROG,(PKT)	; Say no need for Pkt at program level
	JN PINTL,(PKT),REMSE1	; Jump if interrupt will do the INTRBF
	MOVX T1,PT%TDR
	SKIPN RECVF
	  MOVX T1,PT%TDX
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes
	CALL RETPKT		; Return the storage to free area
	JRST REMSE1		; Go look at more of the queue


REMSE7:	SKIPE RECVF		; Processing send retransmit queue?
	  JRST REMSEX		; No
	CAMN PKT,NEXT		; Queue empty now?
	  JRST REMSE8		; Yes.
	XMOVEI T1,RX		; Select the Retransmitter
	MOVE T2,NEXTRX		; Computed next retransmit time
	SUB T2,TODCLK		; Convert to increment
	CALL SIGNAL		; Make the retransmitter run then
	JRST REMSEX

REMSE8:	MOVEI T1,TCBQRX(TCB)
	SETSEC T1,INTSEC
	SKIPE (T1)
	  JFCL ; CALL DQTASK

REMSEX:	POP P,TPKT
	POP P,PKT
	RESTORE
	RET

; ABTCON	Abort a connection

; Clears queues and forces send and receive buffers back to the user
; with the (argument) Code.  The connection is set to Not Synchronized.

; Called both from PRCRST and from the ABORT JSYS.

;TCB/	(Extended) Locked Connection Block
;T1/	Event Code	(ELP+↑D14 - reset)
;			(ELT+↑D4  - no free TVTs)
;	CALL ABTCON
;Ret+1:	always

ABTCON::LOCAL <CODE>
	PUSH P,PKT
	MOVEM T1,CODE

; Buffers flushed via USRBFE/F are placed onto the TCPDBQ by BFRDUN if
; the have wait bits assigned (for wait or interrupt);  others are
; returned directly to free storage.

	CALL FLSSBF		; Flush SEND buffers
	MOVE T1,CODE
	CALL FLSRBF		; Flush RECV buffers

; Flush packets from Retransmission Queue

ABTCOA:	LOAD T1,QNEXT,<+TCBRXQ(TCB)>	; Get first thing on Retrans Q
	CAIN T1,TCBRXQ(TCB)	; Is that the head itself?
	  JRST ABTCOB		; Yes.  The queue is now empty.
	SETSEC T1,INTSEC	; Make extended address
	CALL DQ			; Remove from the Retransmission queue
	SETZRO PPROG,(T1)	; Program level now has no claim on PKT
	JN PINTL,(T1),ABTCOA	; Jump if INTRBF will return the space
	MOVE PKT,T1		; Put pointer in right place for RETPKT
	CALL RETPKT
	JRST ABTCOA

; Flush packets from Received Packet Queue

ABTCOB:	LOAD T1,QNEXT,<+TCBRPQ(TCB)>
	CAIN T1,TCBRPQ(TCB)
	  JRST ABTCOC
	SETSEC T1,INTSEC	; Make extended address
	CALL DQ
	MOVE PKT,T1
	CALL RETPKT
	JRST ABTCOB
ABTCOC:

; Flush any Partially Filled Packet

	NOSKED
	LOAD PKT,TSCPK,(TCB)	; Possible packet
	SETZRO TSCPK,(TCB)	; is gone
	OKSKED
	SKIPE PKT		; IF have one
	  CALL RETPKT		; release it

; Collect buffers for this TCB from TCPBDQ and place them on TCPIDQ.
; Then release them all.

	NOSKED			; Prevent user from snatching bufs
	MOVE T1,TCPIDQ
	CALL ABTCO1		; Collect up all dead ones
	OKSKED
	MOVE T1,TCPIDQ
	MOVE T2,T1
	CALL CLEARQ		; Return all to free storage

; Force state to NOT.NOT

	MOVX T1,NOTSYN
	STOR T1,TSSYN,(TCB)	; Set Send state to Not Synchronized
	STOR T1,TRSYN,(TCB)	; Set Recv state to Not Synchronized

; Notify the user

	MOVE T1,CODE
	CALL USREVT		; Pass the event to the user

; Maybe need a secure close

	SKIPE INTSCR		; Running in Secure mode?
	  CALL SCRCLS		; Send a Secure Close Option

	POP P,PKT
	RESTORE
	RET

; ABTCO1(Q)		Release buffers from Buffer Done Queue

;T1/	(Extended) Pointer to a queue head
;TCB/	(Extended) Locked connection block
;NOSKED				How do we know they are finished??
;	CALL ABTCO1
;Ret+1:	Always.  No buffers on DEADBQ owned by TCB; BIDX bits released

ABTCO1:	LOCAL <NXTBFR,DEADBQ>
	PUSH P,BFR
	MOVEM T1,DEADBQ
	MOVE NXTBFR,TCPBDQ	; Pointer to queue head

ABTCO2:	MOVE BFR,NXTBFR
	LOAD NXTBFR,QNEXT,(BFR)	; Get next item on the list
	SETSEC NXTBFR,INTSEC	; Make extended address
	CAMN BFR,TCPBDQ		; Back to head means done
	  JRST ABTCOX
	LOAD T1,BTCB,(BFR)	; Get owning TCB
	SETSEC T1,INTSEC	; Make extended address
	CAME T1,TCB		; It is this connection?
	  JRST ABTCO2		; Go try next

	MOVE T1,BFR		; Pointer to the item
	CALL DQ			; Remove it
	LOAD T1,BIDX,(BFR)	; Get the wait bit index
	CALL RELWTB		; Release it
	MOVE T1,BFR		; Pointer to the block again
	MOVEI T2,DEADBQ		; Where to stash the buffer for later
	CALL NQ			; Release when not NOSKED
	JRST ABTCO2

ABTCOX:	POP P,BFR
	RESTORE
	RET

; SYNAGN	Return a connection to Synchable state

;TCB/	(Extended) Locked Connection Block
;
;	CALL SYNAGN
;Ret+1:	always

SYNAGN:	PUSH P,BFR
	PUSH P,PKT
	CALL RSTADR		; Restore wild address fields

; Moving Send Left to Send Sequence makes the next incarnation of this
; connection different from the one which has just failed and is
; getting resynched.  Thus, RSTs caused by old copyies of the original
; SYN will be unacceptable and will not wipe out the new incarnation.

	LOAD T1,TSSEQ,(TCB)	; Send sequence = SYN + DATA
	STOR T1,TSLFT,(TCB)	; Where to begin next time.
	MOVX T1,SYNABLE
	STOR T1,TRSYN,(TCB)	; Reset the Recv state

	LOAD BFR,TRCB,(TCB)	; Get current receive buffer
	JUMPE BFR,SYNAG1	; Jump if no receive current buffer
	SETSEC BFR,INTSEC	; Make extended address
	SETZRO TRCB,(TCB)	; Forget about it
	CALL RSTBFR		; Reset it to virgin state (hard to do)
	MOVE T1,BFR		; What to NQ
	MOVEI T2,TCBRBQ(TCB)	; The receive buffer queue
	SETSEC T2,INTSEC	; Make extended address
	LOAD T2,QNEXT,(T2)	; First thing on the queue
	SETSEC T2,INTSEC	; Make extended address
	CALL NQ			; Make the recycled buffer first again
SYNAG1:
	SETZRO TRPP,(TCB)	; Forget about partially processed PKT

SYNAG2:	LOAD T1,QNEXT,<+TCBRPQ(TCB)>	; Receive Packet Queue
	CAIN T1,TCBRPQ(TCB)	; Empty now?
	  JRST SYNAG3		; Yes
	SETSEC T1,INTSEC	; Make extended address
	CALL DQ			; Dequeue the packet
	MOVE PKT,T1		; Put in standard place
	CALL RETPKT		; Return possibly full size packet
	JRST SYNAG2
SYNAG3:

	MOVX T1,SYNABL
	STOR T1,TSSYN,(TCB)	; Reset Send state

	LOAD BFR,TSCB,(TCB)	; Get current send buffer
	JUMPE BFR,SYNAG4	; Jump if none
	SETSEC BFR,INTSEC	; Make extended address
	SETZRO TSCB,(TCB)	; Forget there was one
	CALL RSTBFR		; Reset the buffer
	MOVE T1,BFR
	MOVEI T2,TCBSBQ(TCB)	; Send buffer queue
	SETSEC T2,INTSEC	; Make extended address
	LOAD T2,QNEXT,(T2)	; First thing on the queue
	CALL NQ			; Make recycled buffer first again
SYNAG4:
	MOVEI T1,TCBRXQ(TCB)	; Retransmit queue
	SETSEC T1,INTSEC	; Make extended address
	MOVE T2,T1		; Almost never full size packets!
	CALL CLEARQ		; Clear it

	JE TSPRS,(TCB),SYNAGX	; Check if this end is initiator
	JN <TWLDN,TWLDT,TWLDP>,(TCB),SYNAGX	; Don't send if no 4N host
	MOVE T1,TCPSY0		; 2 second delay to prevent loop if
	CALL DLAYPZ		; Foreign TCB non-x and RST causing us to loop
SYNAGX:	POP P,PKT
	POP P,BFR
	RET

; RSTADR(TCB)		Restore wild address fields

; A delayed duplicate may cause a foreign TCP to emit an RST packet to
; kill what it thinks is a half-open connection here.  If in fact the
; connection has been closed and deleted, there may be a listening
; TCB which CHKADD will find and bind to the source of the RST.
; Subsequently the TCP will just flush the RST and will not emit any
; response to it.  This routine is called to undo the temporary binding.

;TCB/	(Extended) Locked connection block
;
;	CALL RSTADR
;Ret+1:	Always.

RSTADR:	LOAD T1,TOPLH,(TCB)	; Restore original local host
	STOR T1,TLH,(TCB)
	LOAD T1,TOPFH,(TCB)	; Restore original foreign host
	STOR T1,TFH,(TCB)
	LOAD T1,TOPFP,(TCB)	; Restore original foreign port
	STOR T1,TFP,(TCB)

	SETZRO TIPOR,(TCB)	; No received IP option count
	SETZM TCBIR(TCB)	; Nor data
	SETZRO TTPOR,(TCB)	; No received TCP option count
	SETZM TCBTR(TCB)	; Nor data

	SETO T1,		; Don't change options
	CALL TCPUOP		; But re-merge

	SETZ T1,
	CALL TCPMXP		; Undo max packet size

	RET

; IPINI			Initialize IP process block

;	CALL IPINI
;Ret+1:	Always

IPINI::	LOCAL <PRC>
	XMOVEI PRC,IP		; Pointer to process block
; Following are guards against really bad things
	SETZM PRCQ(PRC)		; Be sure queue is empty.
	SETZM PRCLCK(PRC)	; Should never try to lock IP lock!
	SETZM PRCWAK(PRC)	; Be sure to run it promptly!
	SETZM PRCQOF(PRC)	; Clear unused cells
	SETZM PRCWOF(PRC)
	XMOVEI T1,INPROC	; Routine address
	MOVEM T1,PRCROU(PRC)	; Set into the control block
	XMOVEI T1,IPRNCT	; Run counter address
	MOVEM T1,PRCRNC(PRC)
	XMOVEI T1,IPUSE		; CPU usage meter
	MOVEM T1,PRCTMR(PRC)

	MOVX T1,QSZ		; Size of a queue head
	CALL GETBLK		; Queue head must be in Internet section
	  JUMPE T1,IPINIX	; Lose, need better way (lose memory)
	MOVEM T1,TCPIDQ		; Save pointer to it
	CALL INITQ		; Initialize it

	HRROI T1,-1
IPINIX:	RESTORE
	RET


	TNXEND
	END